﻿using System;
using System.Text.RegularExpressions;

namespace FacialClub.Helpers
{
    /// <summary>
    /// Credit Card Validation Utility (CreditCardUtility)
    /// </summary>
    public static class CreditCardUtility
    {
        // MAESTRO/SOLO: (?<Maestro>(?:5018|5020|5038|6304|6759|6761|6763|6334|6767|4903|4905|4911|4936|564182|633110|6333|6759)\\d{8,15})
        // MAESTRO: (?<Maestro>5018)|(?<Maestro>5020)|(?<Maestro>5038)|(?<Maestro>6304)|(?<Maestro>6759)|(?<Maestro>6767)
        // MAESTRO INTERNATIONAL:
        // SOLO: (?<Maestro>6334)|(?<Maestro>6767)|(?<Maestro>4903)|(?<Maestro>4905)|(?<Maestro>4911)|(?<Maestro>4936)|(?<Maestro>564182)|(?<Maestro>633110)|(?<Maestro>6333)|(?<Maestro>6759)
        // VISA DEBIT: (?<VisaDebit>(?:400626|40854749|40940002|41228586|41373337|41378788|418760|41917679|419772|420672|42159294|422793|423769|431072|444001|44400508|44620011|44621354|44625772|44627483|446286|446294|450875|45397|454313|45443235|454742|45672545|46583079|46590150|47511059|47571059|47622069|47634089|48440910|484427|49096079|49218182))
        // VISA CREDIT: (?<Visa>4\\d{3})
        // MASTERCARD CREDIT: (?<MasterCard>5[1-5]\\d{2})
        // MASTERCARD DEBIT: "(?(?<MasterCardDebit>(?:516730|516979|517000|517049|535110|535309|535420|535819|537210|537609|557347|557496|557498|557547))|" +
        // AMEX: (?<Amex>3[47]\\d{2}))([ -]?)(?(DinersClub)(?:\\d{6}\\1\\d{4})|(?(Amex)(?:\\d{6}\\1\\d{5})|(?:\\d{4}\\1\\d{4}\\1\\d{4}))

        /// <summary>
        /// Credit Card Start Code REGEX Check
        /// </summary>
        private const string CardRegex = "^(?:(?<Maestro>(?:5018|5020|5038|6304|6759|6761|6763|6334|6767|4903|4905|4911|4936|564182|633110|6333|6759))|" +
            "(?<VisaDebit>(?:400626|40854749|40940002|41228586|41373337|41378788|418760|41917679|419772|420672|42159294|422793|423769|431072|444001|44400508|44620011|44621354|44625772|44627483|446286|446294|450875|45397879|454313|45443235|454742|45672545|46583079|46590150|47511059|47571059|47622069|47634089|48440910|484427|49096079|49218182|400115|40083739|41292123|417935|419740|419741|41977376|424519|4249623|444000|48440608|48441126|48442855|49173059|49173000|491880))|" +
            "(?<Visa>4\\d{3})|" +
            "(?<MasterCardDebit>(?:516730|516979|517000|517049|535110|535309|535420|535819|537210|537609|557347|557496|557498|557547))|" +
            "(?<MasterCard>5[1-5]\\d{2})|" +
            "(?<Amex>3[47]\\d{2}))([ -]?)(?(DinersClub)(?:\\d{6}\\1\\d{4})|(?(Amex)(?:\\d{6}\\1\\d{5})|(?:(?:\\d{8,15})|(?:\\d{4}\\1\\d{4}\\1\\d{4}))))$";

        /// <summary>
        /// IsValidNumber handles the raw card code without a type
        /// </summary>
        /// <param name="cardNum"></param>
        /// <returns></returns>
        public static bool IsValidNumber(string cardNum)
        {
            //Determine the card type based on the number
            CreditCardTypeType? cardType = GetCardTypeFromNumber(cardNum);

            //Call the base version of IsValidNumber and pass the 
            //number and card type
            return IsValidNumber(cardNum, cardType) ? true : false;
        }

        /// <summary>
        /// IsValidNumber handles the raw card code with type
        /// </summary>
        /// <param name="cardNum"></param>
        /// <param name="cardType"></param>
        /// <returns></returns>
        public static bool IsValidNumber(string cardNum, CreditCardTypeType? cardType)
        {
            //Create new instance of Regex comparer with our 
            //credit card regex pattern
            var cardTest = new Regex(CardRegex);

            //Make sure the supplied number matches the supplied
            //card type
            if (cardTest.Match(cardNum).Groups[cardType.ToString()].Success)
            {
                //If the card type matches the number, then run it
                //through Luhn's test to make sure the number appears correct
                return PassesLuhnTest(cardNum) ? true : false;
            }
            else
                //The card number does not match the card type
                return false;
        }

        /// <summary>
        /// Test CVV Code 
        /// </summary>
        /// <param name="cvvCode"></param>
        /// <param name="cardType"></param>
        /// <returns></returns>
        public static bool IsValidCvvCode(string cvvCode, CreditCardTypeType? cardType)
        {
            int digits = 0;
            switch (cardType)
            {
                case CreditCardTypeType.MasterCard:
                    digits = 3;
                    break;
                case CreditCardTypeType.MasterCardDebit:
                    digits = 3;
                    break;
                case CreditCardTypeType.VisaDebit:
                    digits = 3;
                    break;
                case CreditCardTypeType.Visa:
                    digits = 3;
                    break;
                //case "DISCOVER":
                //case "EUROCARD":
                case CreditCardTypeType.Maestro:
                case CreditCardTypeType.Amex:
                    digits = 4;
                    break;
                default:
                    return false;
            }
            var regEx = new Regex("[0-9]{" + digits + "}");
            return (cvvCode.Length == digits && regEx.Match(cvvCode).Success);
        }

        /// <summary>
        /// SplitNameOnCard
        /// </summary>
        /// <param name="nameOnCard"></param>
        /// <returns></returns>
        public static string[] SplitNameOnCard(string nameOnCard)
        {
            //Nasty
            var returnedSplitNameOnCard = new string[3];
            string[] splitNameOnCard = nameOnCard.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            switch (splitNameOnCard.Length)
            {
                case 1:
                    returnedSplitNameOnCard[0] = splitNameOnCard[0];
                    returnedSplitNameOnCard[1] = splitNameOnCard[0];
                    returnedSplitNameOnCard[2] = splitNameOnCard[0];
                    break;
                case 2:
                    returnedSplitNameOnCard[0] = splitNameOnCard[0];
                    returnedSplitNameOnCard[1] = splitNameOnCard[1];
                    returnedSplitNameOnCard[2] = splitNameOnCard[0];
                    break;
                default:
                    returnedSplitNameOnCard[0] = splitNameOnCard[0];
                    returnedSplitNameOnCard[1] = splitNameOnCard[1];
                    returnedSplitNameOnCard[2] = splitNameOnCard[2];
                    break;
            }
            return returnedSplitNameOnCard;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="mmyyString"></param>
        /// <returns></returns>
        public static string PadMMYYString(string mmyyString)
        {
            string[] mmyySplit = mmyyString.Split(new[] { '/' });
            for (int i = 0; i < mmyySplit.Length; i++)
            {
                mmyySplit[i] = string.Format("{0,2}", mmyySplit[i]).Replace(" ", "0");
            }
            return String.Join(null, mmyySplit);
        }

        public static CreditCardTypeType? GetCardTypeFromNumber(string cardNum)
        {
            //Create new instance of Regex comparer with our
            //credit card regex pattern
            var cardTest = new Regex(CardRegex);

            //Compare the supplied card number with the regex
            //pattern and get reference regex named groups
            GroupCollection gc = cardTest.Match(cardNum).Groups;

            //Compare each card type to the named groups to 
            //determine which card type the number matches
            if (gc[CreditCardTypeType.Amex.ToString()].Success)
            {
                return CreditCardTypeType.Amex;
            }
            else if (gc[CreditCardTypeType.MasterCardDebit.ToString()].Success)
            {
                return CreditCardTypeType.MasterCardDebit;
            }
            else if (gc[CreditCardTypeType.MasterCard.ToString()].Success)
            {
                return CreditCardTypeType.MasterCard;
            }
            else if (gc[CreditCardTypeType.VisaDebit.ToString()].Success)
            {
                return CreditCardTypeType.VisaDebit;
            }
            else if (gc[CreditCardTypeType.Visa.ToString()].Success)
            {
                return CreditCardTypeType.Visa;
            }
            else if (gc[CreditCardTypeType.Maestro.ToString()].Success)
            {
                return CreditCardTypeType.Maestro;
            }
            else
            {
                //Card type is not supported by our system, return null
                //(You can modify this code to support more (or less)
                // card types as it pertains to your application)
                return null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cardTypeCode"></param>
        /// <returns></returns>
        public static CreditCardTypeType? GetCardTypeFromCardTypeCode(string cardTypeCode)
        {
            switch (cardTypeCode)
            {
                case "SW":
                case "SD":
                    return CreditCardTypeType.Maestro;
                case "MasterCard":
                case "MC":
                    return CreditCardTypeType.MasterCard;
                case "MD":
                    return CreditCardTypeType.MasterCardDebit;
                case "VISA":
                case "VI":
                    return CreditCardTypeType.Visa;
                case "VD":
                    return CreditCardTypeType.VisaDebit;
                case "AM":
                    return CreditCardTypeType.Amex;
                //case "BC":
                //    return CreditCardTypeType.BarclaysConnect;
                default:
                    //Card type is not supported by our system, return null
                    //(You can modify this code to support more (or less)
                    // card types as it pertains to your application)
                    return null;
            }
        }

        /// <summary>
        /// Debugging Tool
        /// </summary>
        /// <param name="cardType"></param>
        /// <returns></returns>
        public static string GetCardTestNumber(CreditCardTypeType cardType)
        {
            //Test Numbers from PayPal
            //for testing card transactions are:
            //Credit Card Type              Credit Card Number
            //*American Express             378282246310005
            //*American Express             371449635398431
            //American Express Corporate    378734493671000
            //Diners Club                   30569309025904
            //Diners Club                   38520000023237
            //Discover                      6011111111111117
            //Discover                      6011000990139424
            //*MasterCard                   5555555555554444
            //*MasterCard                   5105105105105100
            //*Visa Credit                  4111111111111111
            //*Visa Debit                   4012888888881881
            //*Maestro (switch/solo)        6331101999990016
            //Src: https://www.paypal.com/en_GB/vhelp/paypalmanager_help/credit_card_numbers.htm
            //Return bogus CC number that passes Luhn and format tests
            switch (cardType)
            {
                case CreditCardTypeType.Amex:
                    return "3782 822463 10005";
                case CreditCardTypeType.MasterCard:
                    return "5105 1051 0510 5100";
                case CreditCardTypeType.MasterCardDebit:
                    return "5105 1051 0510 5100";
                case CreditCardTypeType.Visa:
                    return "4111 1111 1111 1111";
                case CreditCardTypeType.VisaDebit:
                    return "4917 4800 0000 0008";
                case CreditCardTypeType.Maestro:
                    return "6331 1019 9999 0016";
                default:
                    return null;
            }
        }

        /// <summary>
        /// LUHN Algorithm Card Number Test
        /// If Mod 10 equals 0, the number is good and this will return true
        /// </summary>
        /// <param name="cardNumber"></param>
        /// <returns></returns>
        private static bool PassesLuhnTest(string cardNumber)
        {
            var deltas = new[] { 0, 1, 2, 3, 4, -4, -3, -2, -1, 0 };
            int checksum = 0;
            char[] chars = new Regex(@"[^\d]").Replace(cardNumber, "").ToCharArray(); //Extract digits
            for (int i = chars.Length - 1; i > -1; i--)
            {
                int j = (chars[i]) - 48;
                checksum += j;
                if (((i - chars.Length) % 2) == 0)
                    checksum += deltas[j];
            }
            return ((checksum % 10) == 0);
        }
    }

    /// <summary>
    /// CreditCardTypeType copied for PayPal WebPayment Pro API
    /// (If you use the PayPal API, you do not need this definition)
    /// </summary>
    public enum CreditCardTypeType
    {
        Visa,
        VisaDebit,
        MasterCard,
        MasterCardDebit,
        Amex,
        Maestro
    }
}